using System;
using System.Collections.Generic;
using Commons.Models.Timers;
using Commons.Utils;

namespace Commons.Deamons
{
    public class TimerDeamon : ITimerDeamon
    {
        public ICoroutineRunner CoroutineRunner { protected get; set; }

	    protected Dictionary<string, Timer> activeTimers = new Dictionary<string, Timer>();
	    protected Dictionary<string, Timer> timersToAdd = new Dictionary<string, Timer>();
	    protected HashSet<string> timersToRemove = new HashSet<string>();
	    protected bool withinTransaction;
	    protected Dictionary<string, Timer> timersToAddTx = new Dictionary<string, Timer>();
	    protected HashSet<string> timersToRemoveTx = new HashSet<string>();
	    protected DateTime lastResume = DateTime.MinValue;

        public void Init()
        {
            CoroutineRunner.Execute(Update());
        }

	    public void Add(Timer timer)
	    {
		    if (withinTransaction)
		    {
			    Add(timer, timersToAddTx, timersToRemoveTx);
		    }
		    else
		    {
			    Add(timer, timersToAdd, timersToRemove);
		    }
	    }

	    protected void Add(Timer timer, Dictionary<string, Timer> toAdd, HashSet<string> toRemove)
	    {
		    toRemove.Remove(timer.Id);

		    Timer previous;
		    if (toAdd.TryGetValue(timer.Id, out previous))
		    {
			    previous.Dispose();
		    }

		    toAdd[timer.Id] = timer;
	    }

	    public void Remove(string id)
	    {
		    if (withinTransaction)
		    {
			    Remove(id, timersToAddTx, timersToRemoveTx);
		    }
		    else
		    {
			    Remove(id, timersToAdd, timersToRemove);
		    }
	    }

	    protected void Remove(string id, Dictionary<string, Timer> toAdd, HashSet<string> toRemove)
	    {
		    toAdd.Remove(id);
		    toRemove.Add(id);
	    }

	    public bool IsActive(string id)
	    {
		    return activeTimers.ContainsKey(id);
	    }

	    public bool Exists(string id)
	    {
		    if (IsActive(id))
			    return true;
		    return timersToAdd.ContainsKey(id) || timersToAddTx.ContainsKey(id);
	    }

	    public void OnApplicationPause(bool paused)
	    {
		    if (!paused)
		    {
			    lastResume = DateTimeHelper.Current();
		    }
	    }
	    public IEnumerable<object> Update()
	    {
            while (true)
            { 
		        DeactivateTimers(timersToRemove);
		        ActivateTimers(timersToAdd);
		        CheckActiveTimers();
                yield return null;
            }
	    }

	    protected void ActivateTimers(Dictionary<string, Timer> timers)
	    {
		    if (timers.Count > 0)
		    {
			    foreach (Timer timer in timers.Values)
			    {
				    activeTimers[timer.Id] = timer;
			    }
			    timers.Clear();
		    }
	    }

	    protected void DeactivateTimers(HashSet<string> timersIds)
	    {
		    if (timersIds.Count > 0)
		    {
			    foreach (string timerId in timersIds)
			    {
				    activeTimers.Remove(timerId);
			    }
			    timersIds.Clear();
		    }
	    }

	    protected void CheckActiveTimers()
	    {
		    if (activeTimers.Count > 0)
		    {
			    foreach (Timer timer in activeTimers.Values)
			    {
				    if (timer.IsCompleted())
				    {
					    timer.OnComplete(timer.End < lastResume);

					    timersToRemove.Add(timer.Id);
				    }
			    }
		    }
	    }

	    public void BeginTransaction()
	    {
		    withinTransaction = true;
	    }

	    public void Commit()
	    {
		    withinTransaction = false;

		    foreach (string timerId in timersToRemoveTx)
		    {
			    timersToRemove.Add(timerId);
		    }
		    timersToRemoveTx.Clear();

		    foreach (Timer timer in timersToAddTx.Values)
		    {
			    timersToAdd[timer.Id] = timer;
		    }
		    timersToAddTx.Clear();
	    }

	    public void Rollback()
	    {
		    withinTransaction = false;

		    timersToAddTx.Clear();
		    timersToRemoveTx.Clear();
	    }

	    public void Clear()
	    {
		    activeTimers.Clear();
		    timersToAdd.Clear();
		    timersToRemove.Clear();
		    timersToAddTx.Clear();
		    timersToRemoveTx.Clear();
	    }
    }
}